package cn.coder.jdbc.core;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.coder.jdbc.util.JdbcUtils;

/**
 * 连接池核心类
 * 
 * @author YYDF
 *
 */
public final class ConnectionPool {
	private static final Logger logger = LoggerFactory.getLogger(ConnectionPool.class);

	private static final Object lockObj = new Object();
	private final BlockingQueue<Connection> queue;
	private final DbConfig config;
	private final AtomicInteger waitNum = new AtomicInteger(0);// 统计当前等待连接数

	public ConnectionPool(Properties ps) throws SQLException {
		this.config = new DbConfig(ps);
		this.queue = new LinkedBlockingQueue<>();
		init();
	}

	private void init() throws SQLException {
		JdbcUtils.registerDriver(config.getDriverClassName());
		int size = this.config.getInitialSize();
		for (int i = 0; i < size; i++) {
			this.queue.add(newConnection());
		}
	}

	public Connection getConnection() throws SQLException {
		try {
			return borrowConnection();
		} catch (InterruptedException e) {
			logger.error("Borrow connection faild", e);
			throw new SQLException("Borrow connection faild");
		}
	}

	/**
	 * 获取一个数据库连接<br>
	 * 如果链接一直没有归还，会造成死锁
	 * 
	 * @return 可用连接
	 * @throws SQLException
	 * @throws InterruptedException
	 */
	private Connection borrowConnection() throws SQLException, InterruptedException {
		// 从队列获取，队列为空返回null
		Connection conn = this.queue.poll();
		if (conn != null) {
			if (conn.isValid(0)) {
				logger.debug("[{}]Borrowed {}", config.getName(), conn.hashCode());
				return conn;
			}
			JdbcUtils.closeConnection(conn);
			return newConnection();
		}
		// 如果连接池为空，则等待
		if (this.queue.isEmpty()) {
			synchronized (lockObj) {
				waitNum.incrementAndGet();
				lockObj.wait();
				waitNum.decrementAndGet();
			}
		}
		return borrowConnection();
	}

	private Connection newConnection() throws SQLException {
		final Connection conn = DriverManager.getConnection(config.getUrl(), config.getUsername(), config.getPassword());
		logger.debug("[{}]Created {}", config.getName(), conn.hashCode());
		return conn;
	}

	public void releaseConnection(Connection con) {
		//如果小于初始化的2倍，则归还连接池
		if (this.queue.size() < this.config.getInitialSize() * 2) {
			this.queue.add(con);
			logger.debug("[{}]Released {}, Waiting {}", config.getName(), con.hashCode(), waitNum.get());
			if (waitNum.get() > 0) {
				synchronized (lockObj) {
					lockObj.notify();
				}
			}
		} else {
			// 如果一直释放连接，则表示初始值应该加大了
			JdbcUtils.closeConnection(con);
			logger.warn("[{}]Released {} faild and force close", config.getName(), con.hashCode());
		}
	}

	public void clear() {
		logger.debug("[{}]Pool start clear", config.getName());
		for (Connection con : this.queue) {
			JdbcUtils.closeConnection(con);
		}
		this.queue.clear();
		JdbcUtils.deregisterDriver();
	}

	private final class DbConfig {

		private final String name;
		private final int initialSize;
		private final String driverClassName;
		private final String url;
		private final String username;
		private final String password;

		public DbConfig(Properties properties) {
			this.name = properties.getProperty("jdbc.datasource.name");
			this.driverClassName = properties.getProperty("jdbc.datasource.driverClassName");
			String size = properties.getProperty("jdbc.datasource.initialSize");
			this.initialSize = size == null ? 2 : Integer.parseInt(size);
			this.url = properties.getProperty("jdbc.datasource.url");
			this.username = properties.getProperty("jdbc.datasource.username");
			this.password = properties.getProperty("jdbc.datasource.password");
		}

		public int getInitialSize() {
			return initialSize;
		}

		public String getUrl() {
			return url;
		}

		public String getUsername() {
			return username;
		}

		public String getPassword() {
			return password;
		}

		public String getName() {
			return name;
		}

		public String getDriverClassName() {
			return driverClassName;
		}

	}

}
